import {
  Bool,
  Float,
  Iota,
  Null,
  Rune,
  Sint,
  Strings,
  Uint,
} from '../ast/types'

import sign from '../ast/sign'

const

  MAX = Math.floor(Number.MAX_SAFE_INTEGER / 10),
  MAX_HEX = Math.floor(Number.MAX_SAFE_INTEGER / 16),
  MAX_BIN = Math.floor(Number.MAX_SAFE_INTEGER / 2),

  X = sign['0x'],
  B = sign['0b'],
  F = sign['0f'],
  R = sign['\\'],
  S = sign['\''],
  T = sign['`'],
  D = sign['"'],

  INF     = sign.Infinity,
  NAN     = sign.NaN,
  FALSE   = sign.false,
  NULL    = sign.null,
  TRUE    = sign.true

export default function (s, sign) {
  if (sign < 11) {// 0..9
    sign--
    if (sign) return int(s, sign, Sint)
    while (s.rune === 0x30) s.step()
    sign = s.soDigit()
    if (sign < 0) sign = 0
    return int(s, sign, Uint)
  }

  if (sign < INF) switch (sign) {
    case X:
      return hex(s)
    case B:
      return bin(s)
    case S:
      return string(s)
    case D:
      return rune(s)
    case F:
      return binFloat(s)
    case R:
      return regex(s)
    case T:
      return template(s)
  }

  else switch (sign) {
    case FALSE:
      return new Bool(false)
        .start(s.start).end(s.end)
    case TRUE:
      return new Bool(true)
        .start(s.start).end(s.end)
    case NULL:
      return new Null()
        .start(s.start).end(s.end)
    case IOTA:
      return new Iota().start(s.start).end(s.end)
    case INF: // Infinity
      return new Float(0b10, 0, 0)
        .start(s.start).end(s.end)
    case NAN: // NaN
      return new Float(0b11, 0, 0)
        .start(s.start).end(s.end)
  }

  s.fail(sign)
}

function assert (s, v) {
  if (v > MAX) s.fail('out of MAX_SAFE_INTEGER')
}

function assert_hex (s, v) {
  if (v > MAX_HEX) s.fail('out of MAX_SAFE_INTEGER')
}

function assert_bin (s, v) {
  if (v > MAX_BIN) s.fail('out of MAX_SAFE_INTEGER')
}

function int (s, v, type) {
  let
    start = s.start,
    h = v === 1 && 1 || 0,
    fraction = v > 1 && v || 0,
    exponent = v > 1 && 1 || 0

  while (true) {
    let x = s.soDigit()
    if (x < 0) {
      if (s.rune !== 0x2D) break
      x = s.drop().soDigit()
      if (x < 0) s.unexpected()
    }
    v = v * 10 + x
    fraction = fraction * 10 + x
    exponent++
    assert(s, v)
  }

  if (s.rune === 0x2E && s.codePointAt(s.offset + 1) !== 0x2E) {
    let x = s.drop().soDigit()

    if (x < 0) s.unexpected()
    fraction = fraction * 10 + x
    type = Float

    while (true) {
      x = s.soDigit()
      if (x < 0) {
        if (s.rune !== 0x2D) break
        x = s.drop().soDigit()
        if (x < 0) s.unexpected()
      }
      fraction = fraction * 10 + x
      assert(s, fraction)
    }
  }

  if (s.rune === 0x45 || s.rune === 0x65) {
    let e = 0, negative = s.drop().rune === 0x2D

    if (negative || s.rune === 0x2B)
      s.drop()

    type = Float

    while (true) {
      let x = s.soDigit()
      if (x < 0) {
        if (s.rune !== 0x2D) break
        x = s.drop().soDigit()
        if (x < 0) s.unexpected()
      }
      assert(s, e)
      e = e * 10 + x
    }

    if (negative) e = -e
    exponent += e
    if (exponent > 52)
      s.fail('exceeding the exponent ranges for floating')
  }

  if (type !== Float)
    return new type(v).start(start).end(s.end)

  while (fraction && fraction % 10 === 0)
    fraction = fraction / 10

  return new Float(h, fraction, exponent)
    .start(start).end(s.end)
}

function hex (s) {
  let v = 0, x = s.soHex()
  while (true) {
    if (x < 0) s.unexpected()
    assert_hex(s, v)
    v = v * 16 + x
    if (s.rune === 0x2D)
      x = s.drop().soHex()
    else {
      x = s.soHex()
      if (x < 0) break
    }
  }

  return new Uint(v)
    .start(s.start).end(s.end)
}

function rune (s) {
  let cp = s.rune
  if (cp === 0x22)
    return new Rune('')
      .start(s.start).end(s.drop().end)

  if (!s.isVChar() || s.drop().rune !== 0x22)
    s.unexpected()

  return new Rune(s.drop().text())
    .start(s.start).end(s.end)
}

function bin (s) {
  let v = 0, x = s.soBin()
  while (true) {
    if (x < 0) s.unexpected()
    assert_bin(s, v)
    v = v * 2 + x
    if (s.rune === 0x2D)
      x = s.drop().soBin()
    else {
      x = s.soBin()
      if (x < 0) break
    }
  }

  return new Uint(v)
    .start(s.start).end(s.end)
}

function unescapeHex (s) {
  let v = s.soHex()
  if (v >= 0) {
    v = v * 16 + s.soHex()
    if (v < 0) s.unexpected()
    return String.fromCharCode(v)
  }

  if (!s.leftBraces()) s.unexpected()
  v = s.soHex()
  if (v < 0) s.unexpected()
  while (!s.rightBraces()) {
    v = v * 16 + s.soHex()
    if (v < 0) s.unexpected()
    if (v > 0x10FFFF)
      s.fail('illegal unicode codepoint')
  }

  if (v >= 0xD800 && v <= 0xDFFF)
    s.fail('illegal surrogate pair')

  return String.fromCodePoint(v)
}

function unescape (s, cp) {

  if (cp === 0x6e) return '\n'
  if (cp === 0x27) return '\''
  if (cp === 0x5C) return '\\'
  if (cp === 0x78) return unescapeHex(s)
  if (cp === 0x66) return '\f'
  if (cp === 0x72) return '\r'
  if (cp === 0x74) return '\t'
  if (cp === 0x76) return '\v'
  s.unexpected()
}

function trim (s, cols) {
  while (cols && s.rune === 0x20 && s.drop())
    cols--

  if (cols) s.unexpected()
}

function string (s) {
  let cols = s.left - 1, v = ''
  while (s.rune !== 0x27) {
    if (s.rune === 0x5C)
      v += unescape(s, s.drop().step())
    else if (s.isVChar())
      v += String.fromCodePoint(s.step())
    else if (s.nl())
      trim(s.nls(), cols)
    else
      s.unexpected()
  }

  return new Strings(v)
    .start(s.start).end(s.drop().end)
}

function binFloat (s) {
  s.fail('not implemented')
}

function regex (s) {
  s.fail('not implemented')
}

function template (s) {
  s.fail('not implemented')
}
